#include "globalParameters.h"
#include "tx.h"
#include "rx.h"
#include <iostream>
#include <fstream>
#include <cmath>
#include <vector>
#include <cstdlib>


using namespace std;

void readDataFromFile(GlobalParameters,char *);
double getInteference(Rx*, Tx*);
double getInteference(Tx*, Tx*);
double getInteference(int, int, int, int,double);
void generateRandomTopology(char *);

vector<Tx> transmitters;
vector<Rx> receivers;
vector<double> exponentialBacklog;
vector<double> loglogBacklog;

//simulation paramters
double transmitPower = pow(10.0,-1.0);
//setting the global parameters for the simulation
GlobalParameters parameters(15,1023,9,34,3.16*pow(10.0,-11.0),10,500000,1500,9);

//picking a topology

//0 to use defined topology. 1 to use random topology with defined parameters
int topologyToUse = 0;

//for defined topology
char filenameForDefinedTopology []= ".//data.txt";

//for random topology
///int numNodes = 5 + rand()%16;
int numNodes = 10;
//int gridX = 15 + rand()%66;
int gridX = 10;
int gridY = gridX;

char filenameForRandomTopologyOutput[] = ".//randomTopology.txt";
char filenameForAggregateOutput[] = ".//simulationRun.txt";
char filenameForBacklogOuput[] = ".//backlog.csv";

//for doing a simulation run
int protocolsToUse = 2;
int run = 1;
int numberOfRuns = 5;
double channelLoss = 0.5;

double arrivalRateConstant = 0.01;
double arrivalRate = arrivalRateConstant* abs(log2(log2(numNodes))/log2(numNodes)); //per slot



int main(){
	
	
	
	ofstream aggregateOuputFile;
	aggregateOuputFile.open(filenameForAggregateOutput);
	if(!aggregateOuputFile)
	{
		cout << "Aggregate Output file could not be opened." << endl;
	}
	
	//density,noise_level,successes1,failures1,throughput1,fairness1,successes2,failures2,throughput2,fairness2
	
	aggregateOuputFile << "density" << ','
					   << "noise_level" << ','
					   << "successes1" << ','
					   << "failures1" << ','
					   << "throughput1" << ','
					   << "fairness1" << ','
					   << "successes2" << ','
					   << "failures2" << ','
					   << "throughput2" << ','
					   << "fairness2" << endl;
	
	
	int currentRun = 0;
	for(currentRun = 0;currentRun<numberOfRuns;currentRun++){
		
		double percentDone = (currentRun*100.0)/(numberOfRuns * 1.0);
		
		
		
		cout<< "Run "<< (currentRun+1) << " of " << numberOfRuns << ". Percent done:" << percentDone << endl;
		
		//channelLoss = ((rand()%50)*1.0)/100.0;
		
		if(topologyToUse==1){
			generateRandomTopology(filenameForRandomTopologyOutput);
		}
		
		//int totalPacketsE = 0;
		//int totalPacketsL = 0;
		//int currentProtocol;
		for(int currentProtocol=1;currentProtocol<(protocolsToUse+1);currentProtocol++){
			int protocol = currentProtocol;

			//bool burstOn = true;
			//int burstCounter = 0;

			//double backLog = 1.0;
			//cout<< "Running Simulation with protocol ="<<protocol<<endl<<endl;
		/*
		 pseudo-code
		 *set global parameters (done)
		 *get topology data from text file (done)
		 
		 --main loop--
		 
			-calculate signal strength from each transmitter to corresponding receiver
			-calculate intereference for everyone (done)
		 
			-for each receiver if its transmitter is active (done)
				-check if rx can receive. if yes decrease slots to go. if no declare failure on last packet
				-if slots to go is zero say successful on last packet
		 
			-for each transmitter if it is active
				-decrease slots to go
				-if slots to go equals 0. check your receiver's lastpacket. if fail/success then reset things accordingly.
		 
			-else if not active for each transmitter if it can transmit
				-decrease the timetofire
				-if ttf < 0, make it active start transmitting.
				-set slots to go for tx and corresponding rx
			
		 
		 --things to do on success:
		 tx:
		 -increment rounds
		 -call setCwOnFail(protocol,parameteres)
		 -call updateTimeToFire(parameters)
		 -increase successful packets
		 -make yourself inactive
		 rx:
		 
		 --things to do on failure:
		 tx:
		 -increment rounds
		 -call setCwOnSuccess(protocol,parameteres)
		 -call updateTimeToFire(parameters)
		 -increase failed packets
		 -make yourself inactive
		 rx:
		 
		 
			*/
			
		//read data:
			if(topologyToUse==1){
				readDataFromFile(parameters, filenameForRandomTopologyOutput);
			}
			else{
				readDataFromFile(parameters, filenameForDefinedTopology);
			}
			
			
		
		//this is the main loop and goes over the simulation one time step at a time
		int i;
			
			//bool transmisionFinished = false;
			
		for (i=0; i<parameters.getSimulationTime(); i+=parameters.getSlotTime()) {
			
			/*
			//everyone needs to check for more packets
			//transmisionFinished = true;
			//also check for backlogg
			
			backLog = 0.0;
			for (vector<Tx>::iterator currentTx = transmitters.begin();
				 currentTx != transmitters.end();
				 ++currentTx){
				
				//currentTx->checkForMorePackets(arrivalRate,parameters);
				
				
				//calculate backlog
				int currentValue = currentTx->getQueueSize();
				if(currentValue>0){
					backLog = backLog+ (currentValue*1.0);
				}
				
			}
			//backLog = backLog/(1.0* numNodes);
			
			//check backlog
			if(protocol==1){
				exponentialBacklog.push_back(backLog);
			}
			if(protocol==2){
				loglogBacklog.push_back(backLog);
			}
			*/
			
			/*
			//introduce a burst
			burstCounter++;
			if(burstCounter >= waitBeforeBurst && burstOn){
			//add packets to each transmitter
				for (vector<Tx>::iterator currentTx = transmitters.begin();
					 currentTx != transmitters.end();
					 ++currentTx){
					
					int currentQueueSize = currentTx->getQueueSize();
					currentTx->setQueueSize(currentQueueSize+burstSize);
				}
				burstOn = false;
				
			
			}
			*/
			
			
			
			
			
			//here we need to go over each receiver and calculate intereference from all other active transmitters.
			for (vector<Rx>::iterator currentRx = receivers.begin();
				 currentRx != receivers.end();
				 ++currentRx){
				//reset interference
				currentRx->setInterference(currentRx->getNoise());
				//now go over all other transmitters. if id different and active, calculate interference.
				for (vector<Tx>::iterator otherTx = transmitters.begin();
					 otherTx != transmitters.end();
					 ++otherTx){
					//if other transmitter is active and not same as our link
					if(otherTx->getActive() && otherTx->getReceiver()!=currentRx->getId()){
						//double addInteference = getInteference(&(*currentRx),&(*otherTx));
						double addInteference = getInteference(currentRx->getX(),currentRx->getY(),
															   otherTx->getX(),otherTx->getY(),
															   otherTx->getPower());
						
						
						currentRx->incrementInterference(addInteference);
					}
					
					if(currentRx->getId()==otherTx->getId()){
					//calculate signal strength
						//double signal = getInteference(&(*currentRx), &(*otherTx));
						double signal = getInteference(currentRx->getX(),currentRx->getY(),
															   otherTx->getX(),otherTx->getY(),
															   otherTx->getPower());
						
						currentRx->setSignal(signal);
						
						
					}
					
				}
			}
			
			//here we need to go over each transmitter and calculate intereference from all other active transmitters.
			for (vector<Tx>::iterator currentTx = transmitters.begin();
				 currentTx != transmitters.end();
				 ++currentTx){
				
				currentTx->setInterference(currentTx->getNoise());
				//now go over all other transmitters. if id different and active, calculate interference.
				for (vector<Tx>::iterator otherTx = transmitters.begin();
					 otherTx != transmitters.end();
					 ++otherTx){
					//if other transmitter is active and not same as our link
					if(otherTx->getActive() && otherTx->getId()!=currentTx->getId()){
						//double addInteference = getInteference(&(*currentTx),&(*otherTx));
						double addInteference = getInteference(currentTx->getX(),currentTx->getY(),
															   otherTx->getX(),otherTx->getY(),
															   otherTx->getPower());
						
						
						
						currentTx->incrementInterference(addInteference);
					}
				}
			}
			//now intereference for all nodes has been calculated accross the network
			
			
			//here we need to go over each receiver. get its transmitter. if transmitter is active:
			//-check if rx can receive. if yes decrease slots to go. if no declare failure on last packet
			//-if slots to go is zero say successful on last packet
			
			for (vector<Rx>::iterator currentRx = receivers.begin();
				 currentRx != receivers.end();
				 ++currentRx){
		
				//get id for transmitter
				int currentTxId = currentRx->getTransmitter();
				
				//get to the appropriate transmitter.
				for (vector<Tx>::iterator candidateTx = transmitters.begin();
					 candidateTx != transmitters.end();
					 ++candidateTx){
					
					if(candidateTx->getId()==currentTxId && candidateTx->getActive()){
						//we have the tx/rx pair we want to work with and we know tx is active
						//if you can receive decrease your slots
						if(currentRx->canReceive(parameters)){currentRx->decrementSlotsToGo();}
						//if you can't decrease declare failure
						if(!(currentRx->canReceive(parameters))){
							currentRx->setLastPacket(false);
							currentRx->setSlotsToGo(-1);
						}
						//check if you are done.
						if(currentRx->getSlotsToGo()==0){
							//try to fail for channel loss
							double channelLossTry = ((rand()%100)*1.0)/100.0;
							if(channelLossTry < channelLoss){
								currentRx->setLastPacket(false);
								currentRx->setSlotsToGo(-1);
							}
							else{
								currentRx->setLastPacket(true);
								currentRx->setSlotsToGo(-1);
							}
						}
					}

				}

			}
			
			
			
			//-for each transmitter if it is active
				//-decrease slots to go
				//-if slots to go equals 0. check your receiver's lastpacket. if fail/success then reset things accordingly.
			
			//(-else if not active for each transmitter if it can transmit
				//-decrease the timetofire
				//-if ttf < 0, make it active start transmitting.
					//-set slots to go for tx and corresponding rx)
			
			for (vector<Tx>::iterator currentTx = transmitters.begin();
				 currentTx != transmitters.end();
				 ++currentTx){
				
				//get id for receiver
				int currentRxId = currentTx->getReceiver();
				
				//get to the appropriate receiver.
				for (vector<Rx>::iterator candidateRx = receivers.begin();
					 candidateRx != receivers.end();
					 ++candidateRx){
					
					if(currentRxId==candidateRx->getId()){
						//we have our tx/rx pair
						//case 1: it is active
						if(currentTx->getActive()){
						
							//decrement slots to go
							currentTx->decrementSlotsToGo();
							//check if you are done
							if(currentTx->getSlotsToGo()==0){
							
								/*
								--things to do on success:
									tx:
									-increment rounds
									-decrement packets
									-call setCwOnSuccess(protocol,parameteres)
									-call updateTimeToFire(parameters)
									-increase successful packets
									-make yourself inactive
									rx: not sure?
									
									--things to do on failure:
										tx:
										-increment rounds
										-call setCwOnFail(protocol,parameteres)
										-call updateTimeToFire(parameters)
										-increase failed packets
										-make yourself inactive
										rx:not sure?
										
									*/	

								//case 1a: success
								if(candidateRx->successOnLast()){
									currentTx->incrementRounds();
									currentTx->setCwOnSuccess(protocol,parameters,numNodes);
									currentTx->updateTimeToFire(parameters);
									currentTx->incrementSuccessfulPackets();
									currentTx->setActive(false);
								}

								//case 1b: failure
								else{
									currentTx->incrementRounds();
									currentTx->setCwOnFail(protocol,parameters);
									currentTx->updateTimeToFire(parameters);
									currentTx->incrementFailedPackets();
									currentTx->setActive(false);

								}
							
							}
							
							
						
						}
						
						
						
						
						
						
						//case 2: it is not active
						//(-else if not active for each transmitter if it can transmit
						//-decrease the timetofire
						//-if ttf < 0, make it active start transmitting.
						//-set slots to go for tx and corresponding rx)
						else{
						//check if you can transmit. If yes decrease time to fire. if ttf < 0 then fire
							
							if(currentTx->canTransmit(parameters)){
							   currentTx->decreaseTimeToFire(parameters.getSlotTime());
								//now you might want to fire if the value has gone below 0
								if(currentTx->getTimeToFire()<=0){
									currentTx->setActive(true);
									currentTx->setSlotsToGo(parameters.getSlotsPerPacket());
									candidateRx->setSlotsToGo(parameters.getSlotsPerPacket());
								}
								
								
							}

						}

					}
		
				}
				
			}
		
			
		}
			int sumSuccess = 0;
			int sumFail = 0;
			
			double fairness = 0.0;
			double throughput = 0.0;
			
			double density = (numNodes*1.0)/(gridX*gridY*1.0);
			
			
			if(protocol==1){	
				aggregateOuputFile << density << ',' << channelLoss << ',';
			}
			
			
			
			
		//print all the transmitters
		for (vector<Tx>::iterator currentTx = transmitters.begin();
			 currentTx != transmitters.end();
			 ++currentTx){
			
			//currentTx->print();
			
						
		/*	
			aggregateOuputFile << currentTx->getId() << ' '
							   << currentTx->getX() << ' '
							   << currentTx->getY() << ' '
							   << currentTx->getSuccessfulPackets() << ' '
							   << currentTx->getFailedPackets() << endl;
		*/	
			sumSuccess = sumSuccess + currentTx->getSuccessfulPackets();
			if(currentTx->getSuccessfulPackets() < 2){
				
			}
			else{
				fairness = fairness + log2(currentTx->getSuccessfulPackets()); 
			}
			sumFail = sumFail + currentTx->getFailedPackets();
			
		}
			/*
			if(protocol==1){	
				totalPacketsE = sumSuccess;			
			}
			if(protocol==2){
				totalPacketsL = sumSuccess;
			}
			*/
			
			
			
			throughput = (sumSuccess*1.0)/((sumSuccess + sumFail)*1.0);
			aggregateOuputFile << sumSuccess << ',';
			aggregateOuputFile << sumFail << ',';
			aggregateOuputFile << throughput << ',';
			if(protocol<protocolsToUse){
			aggregateOuputFile << fairness << ',';
			}
			else{
			aggregateOuputFile << fairness << endl;
			}
			
			
			
			transmitters.clear();
			receivers.clear();
			
		
		}
		
		/*
		//output backlog info
		
		ofstream backlogOuputFile;
		backlogOuputFile.open(filenameForBacklogOuput);
		if(!backlogOuputFile)
		{
			cout << "Backlog Output file could not be opened." << endl;
		}
		
		int timeStep = 0;
		backlogOuputFile << "Time" << ',' << "Exponential"<< ',' << "Loglog" << endl;

		int increment = 1;
		double sumBacklogE = 0;
		double sumBackLogL = 0;
		
		for(std::vector<double>::size_type loopVariable = 0; loopVariable < exponentialBacklog.size(); loopVariable+=increment) {
			sumBacklogE = sumBacklogE + exponentialBacklog[loopVariable];
			sumBackLogL = sumBackLogL + loglogBacklog[loopVariable];
			backlogOuputFile << timeStep << ',' << exponentialBacklog[loopVariable] << ',' << loglogBacklog[loopVariable] << endl;
			timeStep = timeStep + (increment*parameters.getSlotTime());
		}
		
		backlogOuputFile << (sumBacklogE*1.0)/(totalPacketsE * 1.0) << ',';
		backlogOuputFile << (sumBackLogL*1.0)/(totalPacketsL * 1.0)<< endl;
		
		backlogOuputFile.close();
		 
		exponentialBacklog.clear();
		loglogBacklog.clear();
		*/
	}
	aggregateOuputFile.close();
	return 0;
}


void readDataFromFile(GlobalParameters parameters,char* filename){
	
	//this method reads the topology data from a file. the input format of file is
	//each line has data in the following format.
	//txId txX txY rxId rxX rxY
	//assumption is each transmitter has only one reciever and vice versa
	
	ifstream dataFile;
	dataFile.open(filename);
	if(!dataFile)
	{
		cout << "Input topology file could not be opened. Please check the path again." << endl;
		exit(1);
	}
	
	//cout<<result;
	
	int temp;
	
	while (dataFile >> temp) {
		//we have first value it is tx id.
		int txId = temp;
		int txX;
		dataFile >> txX;
		int txY;
		dataFile >> txY;
		int rxId;
		dataFile >> rxId;
		int rxX;
		dataFile >> rxX;
		int rxY;
		dataFile >> rxY;
		//now we have enough data to make one transmitter, one receiver and their link
		
		//make new transmitter
		Tx tx1(txId,txX,txY,transmitPower,parameters);
		tx1.setReceiver(rxId);
		
		//make new receiver
		Rx rx1(rxId,rxX,rxY,parameters);
		rx1.setTransmitter(txId);
		
		//add them both to the vectors.
		transmitters.push_back(tx1);
		receivers.push_back(rx1);
		
	}

	dataFile.close();

}


double getInteference(Rx *current, Tx *other){

	double interference = 0.0;
	double distance =sqrt(pow((current->getX() - other->getX()),2.0)+pow((current->getY() - other->getY()),2.0));
	interference = 2.52383 * pow(10.0,-5.0) * other->getPower() * pow((1/distance),3.1);
	//2.52383*10^-5*(PowerOfTheTransmitter)*(1/d)^3.1

	return interference;
}

double getInteference(Tx *current, Tx *other){
	
	double interference = 0.0;
	double distance =sqrt(pow((current->getX() - other->getX()),2.0)+pow((current->getY() - other->getY()),2.0));
	interference = 2.52383 * pow(10.0,-5.0) * other->getPower() * pow((1/distance),3.1);
	//2.52383*10^-5*(PowerOfTheTransmitter)*(1/d)^3.1
	
	return interference;
}

double getInteference(int x1, int y1, int x2, int y2,double power){
	
	double interference = 0.0;
	double distance =sqrt(pow((x1 - x2),2.0)+pow(y1 - y2,2.0));
	interference = 2.52383 * pow(10.0,-5.0) * power * pow((1/distance),3.1);
	//2.52383*10^-5*(PowerOfTheTransmitter)*(1/d)^3.1
	
	return interference;
}

void generateRandomTopology(char* filename){
	
	if(run==1){
		numNodes = 3;
		//gridX = 15 + rand()%66;
		gridX = 10;
		gridY = gridX;
	}
	
	ofstream dataFile;
	dataFile.open(filename);
	if(!dataFile)
	{
		cout << "Output file could not be opened" << endl;
		exit(1);
	}
	
	int i;
	for(i = 1;i<=numNodes;i++){
		
		//random tx
		dataFile << i;
		dataFile << ' ';
		dataFile << (rand() % gridX);
		dataFile << ' ';
		dataFile << (rand() % gridY);
		dataFile << ' ';
		
		//random rx
		dataFile << i;
		dataFile << ' ';
		dataFile << (rand() % gridX);
		dataFile << ' ';
		dataFile << (rand() % gridY);
		dataFile << endl;
	
	
	}
	
	dataFile.close();


}

				

				
//format of output
//15,33,33,0.0137741,0.23,28,131,0.176101,11.9248,21,183,0.102941,4.70044

//density,noise_level,successes1,failures1,throughput1,fairness1,successes2,failures2,throughput2,fairness2
